package com.miaocat19.controller;

import com.google.common.util.concurrent.RateLimiter;
import com.miaocat19.error.BusinessException;
import com.miaocat19.error.EmBusinessError;
import com.miaocat19.mq.MqProducer;
import com.miaocat19.response.CommonReturnType;
import com.miaocat19.service.ItemService;
import com.miaocat19.service.OrderService;
import com.miaocat19.service.PromoService;

import com.miaocat19.service.model.UserModel;
import com.miaocat19.util.CodeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @program: Javamiaosha
 * @description: 这个地方我们在分析下下单模块吧
 * <p>
 * 首先是三个参数
 * 我们修改的三个参数 商品id  商品的数量  （非必须）活动的id
 * 首先我们通过httpServletRequest.getParameterMap 获取用户是不是登录成功了
 * <p>
 * 这个判断登录没有登陆 其实有两部校验   一个是从前端获取我们的token_id
 * 第二个是我们获取搭配tokenid 之后我们在进行redis数据库的校验
 * <p>
 * 这样之后我们才开始我们的下单的过程
 * 在这个通过redis获取用户的途中 我们就获取了用户的信息
 * <p>
 * 在创建订单的时候我们也同时加入了    用户的id   这样就创建了四个参数
 * 哈哈 四个参数
 * <p>
 * 下面的创建订单的方法比较复杂
 * 因此我们就进入service层进行分析
 * @author: miaocat19
 * @create: 2021-12-11 19:53
 **/
@Controller("order")
@RequestMapping("/order")
@CrossOrigin(origins = {"*"}, allowCredentials = "true")
public class OrderController extends BaseContoller {


    @Autowired
    private HttpServletRequest httpServletRequest;

    @Autowired
    private OrderService orderService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MqProducer mqProducer;

    @Autowired
    private ItemService itemService;

    @Autowired
    private PromoService promoService;

    private ExecutorService executorService;

    private RateLimiter orderCreateRateLimiter;

    /*初始化一个线程池做队列*/
    @PostConstruct
    public void init() {
        executorService = Executors.newFixedThreadPool(20);

        orderCreateRateLimiter = RateLimiter.create(300);
    }


    //生成验证码
    @RequestMapping(value = "/generateverifycode", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public void generateverifycode(HttpServletResponse response) throws BusinessException, IOException {
        /*这个地方我们判断是不是已经登陆的了*/
        String token = httpServletRequest.getParameterMap().get("token")[0];

        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登陆，不能生成验证码");
        }

        /*获取用户的信息*/
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);

        if (userModel == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登陆，不能生成验证码");
        }

        /*这个地方使用工具类生成验证码*/
        Map<String, Object> map = CodeUtil.generateCodeAndPic();
        /*我们需要把生成的验证码放到 redis中   然后设置一个过期的时间*/
        redisTemplate.opsForValue().set("verify_code_" + userModel.getId(), map.get("code"));
        redisTemplate.expire("verify_code_" + userModel.getId(), 10, TimeUnit.MINUTES);

        /*这个还是使用工具类使用的  通过io流的方式放给前端进行展示*/
        ImageIO.write((RenderedImage) map.get("codePic"), "jpeg", response.getOutputStream());
    }

    //生成秒杀令牌  生成抢课令牌
    @RequestMapping(value = "/generatetoken", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType generatetoken(@RequestParam(name = "itemId") Integer itemId, @RequestParam(name = "promoId") Integer promoId, @RequestParam(name = "verifyCode") String verifyCode) throws BusinessException {

        //根据token获取用户信息  我们获取的信息 还是用户的信息
        //用户的信息 因为秒令牌的生成是需要的用户信息的 用户信息  令人喜欢的课程的信息   还有这个令人喜欢的课程的基本的信息
        String token = httpServletRequest.getParameterMap().get("token")[0];

        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登陆，不能下单");
        }

        //获取用户的登陆信息
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);

        if (userModel == null) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登陆，不能下单");
        }

        //通过verifycode验证验证码的有效性
        String redisVerifyCode = (String) redisTemplate.opsForValue().get("verify_code_" + userModel.getId());

        if (StringUtils.isEmpty(redisVerifyCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "请求非法");
        }
        if (!redisVerifyCode.equalsIgnoreCase(verifyCode)) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "请求非法，验证码错误");
        }


        /*获取秒杀令牌*/  //这个地方 就是生成了  传入相应的信息之后 之前我们还需要做一层判断 因为我们之前不是生成图片实行的 验证码了嘛
        //因此 我们就生成相应的需要使用我们用户的信息 进行判断哈  验证码也是对每个用户唯一的 因为我们当时在生成验证码的时候就hi是这样的生成的
        String promoToken = promoService.generateSecondKillToken(promoId, itemId, userModel.getId());

        //这个地方我们需要看看是不是生成令牌成功了
        if (promoToken == null) {
            throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "生成令牌失败");
        }

        //返回对应的结果 返回相应的令牌哈
        return CommonReturnType.create(promoToken);
    }


    @RequestMapping(value = "/createorder", method = {RequestMethod.POST}, consumes = {CONTENT_TYPE_FORMED})
    @ResponseBody
    public CommonReturnType createOrder(@RequestParam(name = "itemId") Integer itemId, @RequestParam(name = "amount") Integer amount, @RequestParam(name = "promoId", required = false) Integer promoId, @RequestParam(name = "promoToken", required = false) String promoToken) throws BusinessException {


        if (!orderCreateRateLimiter.tryAcquire()) {
            throw new BusinessException(EmBusinessError.RATELIMIT);
        }


        //Boolean login = (Boolean) httpServletRequest.getSession().getAttribute("IS_LOGIN");


        //我们这里使用token进行代替 redis中的存入的token进行判断我们这个用户是否登陆了
        String token = httpServletRequest.getParameterMap().get("token")[0];

        /*if (login == null || !login.booleanValue()) {
            throw new BusinessException(EmBusinessError.USER_LOGIN_FAIL, "用户没有登陆，不能下单");
        }*/

        if (StringUtils.isEmpty(token)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登陆，不能下单");
        }


        //UserModel userModel = (UserModel) httpServletRequest.getSession().getAttribute("LOGIN_USER");

        //获取用户的信息
        UserModel userModel = (UserModel) redisTemplate.opsForValue().get(token);

        if (StringUtils.isEmpty(userModel)) {
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN, "用户还未登陆，不能下单");
        }

        //校验秒杀令牌是否正确
        if (promoId != null) {
            String inRedisPromoToken = (String) redisTemplate.opsForValue().get("promo_token_" + promoId + "_userid_" + userModel.getId() + "_itemid_" + itemId);

            if (inRedisPromoToken == null) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "秒杀令牌校验失败");
            }

            if (!org.apache.commons.lang3.StringUtils.equals(promoToken, inRedisPromoToken)) {
                throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR, "秒杀令牌校验失败");
            }
        }


        new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return null;
            }
        };

        /*下面的这个地方才是最吃内存的
         * 所以我们要用队列来控制这个流量
         * 用20大小的等待队列，来队列化泄洪*/
        Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                /*如果没有我们就初始化存库的流水*/
                String stockLogId = itemService.initStockLog(itemId, amount);

                //OrderModel orderModel = orderService.createOrder(userModel.getId(), itemId, promoId, amount);
                if (!mqProducer.transactionasyncReduceStock(userModel.getId(), itemId, promoId, amount, stockLogId)) {
                    throw new BusinessException(EmBusinessError.UNKNOWN_ERROR, "下单失败");
                }
                return null;
            }
        });


        try {
            future.get();
        } catch (InterruptedException e) {
            throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);
        } catch (ExecutionException e) {
            throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);
        }


        return CommonReturnType.create(null);
    }
}
